﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using smp.ButterflyCakes.Domain.Interfaces;
using smp.ButterflyCakes.Models;
using smp.ButterflyCakes.Models.InputModels.Order;
using smp.ButterflyCakes.Models.ViewModels.Order;
using smp.ButterflyCakes.Repository.Implementation;
using smp.ButterflyCakes.Repository.Interfaces;

namespace smp.ButterflyCakes.Domain.Implementation
{
    public class ShapeBuilder : IShapeBuilder
    {
        private readonly IDomainCakeRepository _repository;

        public ShapeBuilder()
            :this(new DomainCakeRepository())
        {
        }

        public ShapeBuilder(IDomainCakeRepository repository)
        {
            _repository = repository;
        }

        #region Implementation of IShapeBuilder

        public ShapeBuilderCalculatedView ExecutePortionsCalculation(ShapeBuilderCalculationInput model)
        {
            var shape = new ShapeMatrix(model.SelectedCells);
            var viewModel = new ShapeBuilderCalculatedView();
            for (var p = model.Portions; p < Math.Ceiling(model.Portions * 1.50); p++)
            {
                shape.CalculateOverallSize(p, model.IsParty);
                var item = GetItem(shape, p);
                if (ShouldAdd(viewModel, item))
                {
                    viewModel.Items.Add(item);
                }
            }
            return viewModel;
        }

        public bool SaveSizesToCake(ShapeBuilderInput model)
        {
            var attachment = CreateShapeBuilderAttachment(model);
            return _repository.SaveShapeBuilderToCake(model, attachment);
        }

        private bool ShouldAdd(ShapeBuilderCalculatedView viewModel, ShapeBuilderCalculatedItem item)
        {
            if (viewModel.Items.Count == 0)
                return true;

            var foundAMatch = false;
            foreach (var existing in viewModel.Items)
            {
                if (existing.Sizes.Count != item.Sizes.Count)
                    continue;

                var allSizesMatch = true;
                for (int i = 0; i < item.Sizes.Count; i++)
                {
                    if (item.Sizes[i].Length != existing.Sizes[i].Length
                        || item.Sizes[i].Width != existing.Sizes[i].Width)
                    {
                        allSizesMatch = false;
                    }
                }
                if (allSizesMatch)
                    foundAMatch = true;
            }
            return !foundAMatch;
        }

        private ShapeBuilderCalculatedItem GetItem(ShapeMatrix matrix, int portions)
        {
            if (matrix.Length <= 12 && matrix.Width <= 12)
            {
                return new ShapeBuilderCalculatedItem
                           {
                               Portions = portions,
                               Sizes = new List<ShapeBuilderCalculatedItemSize> { new ShapeBuilderCalculatedItemSize { Length = matrix.Length, Width = matrix.Width } },
                               Waste = matrix.Waste,
                               OverallLength = matrix.Length,
                               OverallWidth = matrix.Width
                           };
            }
            var lengths = GetLenthSplitItems(matrix);
            var widths = GetWidthSplitItems(matrix);
            if (lengths == null || widths == null)
                return null;

            var item = new ShapeBuilderCalculatedItem
                           {
                               Portions = portions,
                               Waste = matrix.Waste,
                               Sizes = new List<ShapeBuilderCalculatedItemSize>(),
                               OverallLength = matrix.Length,
                               OverallWidth = matrix.Width
                           };

            if (matrix.Length > 12 && matrix.Width <= 12)
            {
                foreach (var length in lengths)
                {
                    item.Sizes.Add(new ShapeBuilderCalculatedItemSize
                                       {
                                           Length = length,
                                           Width = matrix.Width
                                       });
                }
                return item;
            }
            if (matrix.Width > 12 && matrix.Length <= 12)
            {
                foreach (var width in widths)
                {
                    item.Sizes.Add(new ShapeBuilderCalculatedItemSize
                    {
                        Length = matrix.Length,
                        Width = width
                    });
                }
                return item;
            }

            foreach (var length in lengths)
            {
                foreach (var width in widths)
                {
                    item.Sizes.Add(new ShapeBuilderCalculatedItemSize
                    {
                        Length = length,
                        Width = width
                    });
                }
            }
            return item;
        }

        private List<int> GetLenthSplitItems(ShapeMatrix matrix)
        {
            var sizes = new List<int>();
            if (matrix.Length <= 24)
            {
                if (matrix.Length % 2 == 0)
                {
                    sizes.Add(matrix.Length / 2);
                    sizes.Add(matrix.Length / 2);
                }
                else
                {
                    sizes.Add((matrix.Length - 1) / 2);
                    sizes.Add(((matrix.Length - 1) / 2) + 1);
                }
            }
            else if (matrix.Length <= 36)
            {
                if (matrix.Length % 3 == 0)
                {
                    sizes.Add(matrix.Length / 3);
                    sizes.Add(matrix.Length / 3);
                    sizes.Add(matrix.Length / 3);
                }
                else if ((matrix.Length - 1) % 3 == 0)
                {
                    sizes.Add((matrix.Length - 1) / 3);
                    sizes.Add((matrix.Length - 1) / 3);
                    sizes.Add(((matrix.Length - 1) / 3) + 1);
                }
                else
                {
                    sizes.Add((matrix.Length - 2) / 3);
                    sizes.Add((matrix.Length - 2) / 3);
                    sizes.Add(((matrix.Length - 2) / 3) + 2);
                }
            }
            else if (matrix.Length <= 48)
            {
                if (matrix.Length % 4 == 0)
                {
                    sizes.Add(matrix.Length / 4);
                    sizes.Add(matrix.Length / 4);
                    sizes.Add(matrix.Length / 4);
                    sizes.Add(matrix.Length / 4);
                }
                else if ((matrix.Length - 1) % 4 == 0)
                {
                    sizes.Add((matrix.Length - 1) / 4);
                    sizes.Add((matrix.Length - 1) / 4);
                    sizes.Add((matrix.Length - 1) / 4);
                    sizes.Add(((matrix.Length - 1) / 4) + 1);
                }
                else if ((matrix.Length - 2) % 4 == 0)
                {
                    sizes.Add((matrix.Length - 2) / 4);
                    sizes.Add((matrix.Length - 2) / 4);
                    sizes.Add((matrix.Length - 2) / 4);
                    sizes.Add(((matrix.Length - 2) / 4) + 2);
                }
                else
                {
                    sizes.Add((matrix.Length - 3) / 4);
                    sizes.Add((matrix.Length - 3) / 4);
                    sizes.Add((matrix.Length - 3) / 4);
                    sizes.Add(((matrix.Length - 3) / 4) + 3);
                }
            }
            else
            {
                return null;
            }
            return sizes;
        }

        private List<int> GetWidthSplitItems(ShapeMatrix matrix)
        {
            var sizes = new List<int>();
            if (matrix.Width <= 24)
            {
                if (matrix.Width % 2 == 0)
                {
                    sizes.Add(matrix.Width / 2);
                    sizes.Add(matrix.Width / 2);
                }
                else
                {
                    sizes.Add((matrix.Width - 1) / 2);
                    sizes.Add(((matrix.Width - 1) / 2) + 1);
                }
            }
            else if (matrix.Width <= 36)
            {
                if (matrix.Width % 3 == 0)
                {
                    sizes.Add(matrix.Width / 3);
                    sizes.Add(matrix.Width / 3);
                    sizes.Add(matrix.Width / 3);
                }
                else if ((matrix.Width - 1) % 3 == 0)
                {
                    sizes.Add((matrix.Width - 1) / 3);
                    sizes.Add((matrix.Width - 1) / 3);
                    sizes.Add(((matrix.Width - 1) / 3) + 1);
                }
                else
                {
                    sizes.Add((matrix.Width - 2) / 3);
                    sizes.Add((matrix.Width - 2) / 3);
                    sizes.Add(((matrix.Width - 2) / 3) + 2);
                }
            }
            else if (matrix.Width <= 48)
            {
                if (matrix.Width % 4 == 0)
                {
                    sizes.Add(matrix.Width / 4);
                    sizes.Add(matrix.Width / 4);
                    sizes.Add(matrix.Width / 4);
                    sizes.Add(matrix.Width / 4);
                }
                else if ((matrix.Width - 1) % 4 == 0)
                {
                    sizes.Add((matrix.Width - 1) / 4);
                    sizes.Add((matrix.Width - 1) / 4);
                    sizes.Add((matrix.Width - 1) / 4);
                    sizes.Add(((matrix.Width - 1) / 4) + 1);
                }
                else if ((matrix.Width - 2) % 4 == 0)
                {
                    sizes.Add((matrix.Width - 2) / 4);
                    sizes.Add((matrix.Width - 2) / 4);
                    sizes.Add((matrix.Width - 2) / 4);
                    sizes.Add(((matrix.Width - 2) / 4) + 2);
                }
                else
                {
                    sizes.Add((matrix.Width - 3) / 4);
                    sizes.Add((matrix.Width - 3) / 4);
                    sizes.Add((matrix.Width - 3) / 4);
                    sizes.Add(((matrix.Width - 3) / 4) + 3);
                }
            }
            else
            {
                return null;
            }

            return sizes;
        }

        private DiagramItem CreateShapeBuilderAttachment(ShapeBuilderInput model)
        {
            var activeCells = model.SelectedCells;
            var html = new StringBuilder();
            html.Append("<div><ol class=\"shapeBuilderAttachment\">");

            for (var vertical = 0; vertical < 25; vertical++)
            {
                for (var horizontal = 1; horizontal < 26; horizontal++)
                {
                    var cell = horizontal + (vertical * 25);
                    html.Append("<li class=\"selectable-attachment");
                    if(activeCells.Contains(cell))
                    {
                        html.AppendFormat(" selectable-selected");
                    }
                    html.Append("\"></li>");
                }
            }
            html.Append("</ol></div>");

            return new DiagramItem
                       {
                           Id = Guid.NewGuid(),
                           ComponentId = model.ComponentId,
                           OrderId = model.OrderId,
                           Html = html.ToString()
                       };
        }

        #endregion
    }

    internal class ShapeMatrix
    {
        private readonly List<int> _rows = new List<int>();
        private readonly Dictionary<int, int> _rowActiveCells = new Dictionary<int, int>();
        private readonly Dictionary<int, int> _columnActiveCells = new Dictionary<int, int>();
        private readonly List<int> _columns = new List<int>();
        private readonly List<ShapeCell> _cells = new List<ShapeCell>();
        private float _currentSurfaceArea;
        private float _activeSurfaceArea;

        internal ShapeMatrix(int[] selectedCells)
        {
            var allCells = GetAllCells(selectedCells);
            var rows = new List<int>();
            var columns = new List<int>();

            for (var row = 0; row < 25; row++)
            {
                var rowCells = allCells.FindAll(x => x.IsSelected && x.Row == row);
                if (rowCells.Count == 0)
                    continue;
                if (!rows.Contains(row))
                {
                    rows.Add(row);
                }

                for (var column = 0; column < 25; column++)
                {
                    var cell = rowCells.Find(x => x.IsSelected && x.Column == column + 1);
                    if (cell == null)
                        continue;

                    if (!columns.Contains(column))
                    {
                        columns.Add(column);
                    }

                    cell.Row = row;
                    cell.Column = column;
                    _cells.Add(cell);
                }
            }

            _rows = rows.OrderBy(x => x).ToList();
            _columns = columns.OrderBy(x => x).ToList();

            SetActiveCellSummary();
        }

        internal int Length { get; private set; }

        internal int Width { get; private set; }

        internal int Waste { get; private set; }

        private List<ShapeCell> GetAllCells(int[] selectedCells)
        {
            var allCells = new List<ShapeCell>();
            for (var vertical = 0; vertical < 25; vertical++)
            {
                for (var horizontal = 1; horizontal < 26; horizontal++)
                {
                    var cell = new ShapeCell(vertical, horizontal);
                    cell.IsSelected = selectedCells.Contains(cell.Cell);
                    allCells.Add(cell);
                }
            }
            return allCells;
        }

        private void SetActiveCellSummary()
        {
            foreach (var row in _rows)
            {
                var numberOfActiveCells = _cells.Count(x => x.Row == row && x.IsSelected);
                if (!_rowActiveCells.ContainsKey(row))
                {
                    _rowActiveCells.Add(row, numberOfActiveCells);
                }
                else
                {
                    _rowActiveCells[row] = numberOfActiveCells;
                }
            }

            foreach (var column in _columns)
            {
                var numberOfActiveCells = _cells.Count(x => x.Column == column && x.IsSelected);
                if (!_columnActiveCells.ContainsKey(column))
                {
                    _columnActiveCells.Add(column, numberOfActiveCells);
                }
                else
                {
                    _columnActiveCells[column] = numberOfActiveCells;
                }
            }

            _currentSurfaceArea = _rows.Last() * _columns.Last();
            _activeSurfaceArea = _cells.Count(x => x.IsSelected);
        }

        internal void CalculateOverallSize(int portions, bool isPartyPortions)
        {
            float currentPortions = _activeSurfaceArea;
            if (isPartyPortions)
            {
                currentPortions = _activeSurfaceArea / 2;
            }

            float currentWidth = _rows.Last();
            float currentLength = _columns.Last();
            //var rectangleAspect = currentLength/currentWidth;

            var targetWidth = Math.Sqrt((portions * (_currentSurfaceArea / currentPortions)) / (currentLength / currentWidth));
            var targetLength = targetWidth * (currentLength / currentWidth);

            Length = (int)Math.Ceiling(targetLength);
            Width = (int)Math.Ceiling(targetWidth);
            Waste = 100 - (int)((_activeSurfaceArea / _currentSurfaceArea) * 100);
        }
    }

    internal class ShapeCell
    {
        public ShapeCell(int row, int column)
        {
            Row = row;
            Column = column;
            Cell = Column + (Row * 25);
        }

        public int Cell { get; set; }
        public int Row { get; set; }
        public int Column { get; set; }
        public bool IsSelected { get; set; }
    }
}
